package cl.utalca.robot.service.impl;

import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;


import cl.utalca.robot.domain.TestCaseDTO;
import cl.utalca.robot.domain.TestCaseResultDTO;
import cl.utalca.robot.domain.TestDTO;
import cl.utalca.robot.domain.TestResultDTO;
import cl.utalca.robot.service.RobotService;
import cl.utalca.robot.thread.InputThread;

@Service
public class RobotImpl implements RobotService {

	private static Scanner scanner;
	Logger logger = LoggerFactory.getLogger(RobotImpl.class); 

	public static void exec(TestDTO program){

		Runtime r = Runtime.getRuntime();
		try {

			Process process = r.exec(program.getLanguage().getCompileCommand() + program.getFilename());

			scanner = new Scanner(process.getInputStream());		


			while (scanner.hasNext()){

				System.out.println("SALIDA");
				System.out.println(scanner.next());
			}

			scanner = new Scanner(process.getErrorStream());			
			while (scanner.hasNext()){

				System.out.println("ERROR");
				System.out.println(scanner.next());

				process.destroy();
			}

			Process process2 = r.exec(program.getLanguage().getExecuteCommand() + program.getFilename());

			scanner = new Scanner(process2.getInputStream());
			while (scanner.hasNext()){

				System.out.println("SALIDA");
				System.out.println(scanner.next());
			}

			scanner = new Scanner(process2.getErrorStream());				
			while (scanner.hasNext()){

				System.out.println("ERROR");
				System.out.println(scanner.next());
			}

		}
		catch (IOException e) {
			e.printStackTrace();
		}
	}

	private static Boolean analizeCode(Runtime r, TestDTO p){


		Process process;
		try {

			String filepath = p.getLanguage().getCompileCommand().replace("%C", p.getFile().getPath());
			//String filepath = p.getLanguage().getCompileCommand() + " " + p.getFile().getPath();
			process = r.exec(filepath);
		} catch (IOException e) {

			System.err.println("Error en generar el proceso");
			e.printStackTrace();
			return false;
		}

		scanner = new Scanner(process.getErrorStream());
		Boolean error = false;
		while (scanner.hasNext()){

			System.err.println(scanner.next());

			error = true;
		}		
		process.destroy();
		if(error)
			return false;
		else
			return true;
	}

	private static Map<Integer, TestCaseResultDTO> analizeTestCases(Runtime r, TestDTO p){

		TestCaseResultDTO result = null;

		Map<Integer, TestCaseResultDTO> testResults = new HashMap<Integer, TestCaseResultDTO>();
		
		ExecutorService executor = Executors.newSingleThreadExecutor();
		
		Process process = null;

		
		
		int i = 0;
		for(TestCaseDTO testCase : p.getTestCasesList())
		{			
			try {

				String path = preparePath(p.getFile(), p);
				process = r.exec( path );
			} 
			catch (IOException e) {

				e.printStackTrace();
			}
			
			try {
				Future<TestCaseResultDTO> resultado = executor.submit(new InputThread("thread " + i, testCase, process));
				result = resultado.get(5, TimeUnit.SECONDS);
				
				//results = executor.invokeAll(Arrays.asList(new InputThread("thread " + i, testCase, r, p.getFile(), p)), 10, TimeUnit.SECONDS);
				
				testResults.put(i, result);
			} catch (InterruptedException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}			
			catch (CancellationException ex) {
				ex.printStackTrace();
			}
			catch (ExecutionException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (TimeoutException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				Thread.currentThread().interrupt();
				result = new TestCaseResultDTO();
				result.setResult("TIMEOUT");
				
			}
			testResults.put(i, result);
			process.destroy();			
			i++;
		}
		
		executor.shutdownNow();

		return testResults;
	}

	private static TestCaseResultDTO runTestCase(TestCaseDTO testCase, Runtime r, File file, TestDTO test) {

		Process process = null;
		String result = null;


		try {

			String path = preparePath(file, test);
			process = r.exec( path );
		} 
		catch (IOException e) {

			result = "Error en generar proceso";
		}


		PrintStream inputPrinter = new PrintStream(process.getOutputStream());
		inputPrinter.print(testCase.getInput());


		scanner = new Scanner(process.getInputStream());

		inputPrinter.close();
		String salida = "";

		while (scanner.hasNextLine()){

			salida += scanner.nextLine();
			System.out.println(salida);

		}

		int resultado = checkInputOutput(testCase.getOutput(), salida);

		switch (resultado) {
		case 1:

			result = "CODIGO CORRECTO";
			break;

		case 2:

			result = "ERROR DE FORMATO";
			break;
		default:

			result = "CODIGO ERRONEO";
			break;
		}

		scanner = new Scanner(process.getErrorStream());

		String error = "";
		while (scanner.hasNextLine()){

			error += scanner.nextLine();
		}			

		if(!error.isEmpty())
			System.out.println(error);

		process.destroy();

		TestCaseResultDTO testCaseResult = new TestCaseResultDTO();
		testCaseResult.setResult(result);
		testCaseResult.setRunningOutput(salida);
		testCaseResult.setTestCase(testCase);

		return testCaseResult;
	}

	private static String preparePath(File file, TestDTO test ) {

		String[] pathArray = file.getPath().split("\\\\");
		String pathWithoutName = "";

		for(int i = 0; i < pathArray.length-1; i++)
		{
			pathWithoutName += pathArray[i] + "\\";
		}
		pathWithoutName += " " + pathArray[pathArray.length-1].split("\\.")[0];


		//String path = test.getLanguage().getExecuteCommand() + " " + pathWithoutName;
		String path = test.getLanguage().getExecuteCommand().replace("%C", pathWithoutName);

		return path;
	}

	private static int checkInputOutput(String testOutput, String realOutput){

		if(testOutput.equals(realOutput)){

			return 1;
		}
		else if(testOutput.equalsIgnoreCase(realOutput.trim())){

			return 2;
		}
		else 
			return 3;
	}

	@Override
	public TestResultDTO doTest(TestDTO test){

		TestResultDTO resp = new TestResultDTO();

		Runtime r = Runtime.getRuntime();

		resp.setHasCompilationError(analizeCode(r, test));		

		if(resp.getHasCompilationError()){
			resp.setTestCasesResults(analizeTestCases(r, test));

			Boolean success = true;
			for (TestCaseResultDTO testCaseResult : resp.getTestCasesResults().values())
			{
				if(!testCaseResult.getResult().equals("CODIGO CORRECTO"))
				{
					success = false;
					break;
				}
			}

			resp.setSuccess(success);
		}
		else
			resp.setSuccess(false);

		return resp;
	}

}
